/*
 * Licensed to the 南京汇海交通科技有限公司
 *
 * Copyright (c) 2025.
 * All rights reserved.
 *
 * 本软件属南京汇海交通科技有限公司所有，在未获得南京汇海交通科技有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.chframework.poi;

import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.util.List;
import java.util.TreeMap;
import java.util.UUID;
import java.util.function.Consumer;

import com.chframework.common.extend.ServletHelper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

/**
 * 描述：读写Excel文件工具类
 * 日期：2022-02-28
 * 作者：周孝兵
 * 备注：在实际使用中，可基于ExcelDocument在ExcelUtils中继续扩展满足不同场景的通用方法
 */
@Slf4j
@UtilityClass
@SuppressWarnings("unused")
public final class ExcelUtils {

    // region 读取(导入)操作【默认仅读取了第一个Sheet页，但是ExcelDocument已经支持在单个Excel文件中同时读取若干个Sheet页，可根据需要扩展】

    /**
     * 读取Excel文件，获取每行单元格的值列表
     */
    public static List<TreeMap<String, String>> readForList(InputStream inpStream) {
        ExcelDocument workbook = new ExcelDocument(inpStream);
        List<TreeMap<String, String>> dataGridRowList = workbook.readForList();
        workbook.closeWorkbook();
        return dataGridRowList;
    }

    /**
     * 读取Excel文件，获取 Java POJO 列表
     */
    public static <T extends Object & Serializable> List<T> readForList(InputStream inpStream, Class<T> clazz, int headerSize) {
        List<TreeMap<String, String>> dataGridRowList = ExcelUtils.readForList(inpStream); // 获取整个Sheet页单元格的值集合
        return ExcelConverter.from(dataGridRowList, clazz, headerSize); // 所以，这里一定要排除表头部分
    }

    /**
     * 读取Excel文件，获取特定单元格的值
     */
    public static TreeMap<String, String> readForObject(InputStream inpStream, String... cellCoordinateArray) {
        ExcelDocument workbook = new ExcelDocument(inpStream);
        TreeMap<String, String> dataGridMap = workbook.readForObject(cellCoordinateArray);
        workbook.closeWorkbook();
        return dataGridMap;
    }

    /**
     * 读取Excel文件，获取 Java POJO
     */
    public static <T extends Object & Serializable> T readForObject(InputStream inpStream, Class<T> clazz, String... cellCoordinateArray) {
        TreeMap<String, String> dataGridMap = ExcelUtils.readForObject(inpStream, cellCoordinateArray); // 获取了具体单元格的值集合
        return ExcelConverter.from(dataGridMap, clazz); // 所以，这里确认是不可能存在表头的
    }

    // endregion

    // region 使用模板文件写入(导出)操作【支持Sheet页的复制，支持根据模板同时导出若干个Sheet页】

    /**
     * 读取Excel文件，写入列表
     */
    public static void writeTemplateWithList(File file, List<TreeMap<String, String>> dataGridRowList, int headerSize) {
        ExcelUtils.exportForBrowser(
                new ExcelDocument(file),
                file.getName(),
                (wb) -> wb.writeTemplateWithList(dataGridRowList, "A" + (headerSize + 1)) // 从表头的下一行开始写
        );
    }

    /**
     * 读取Excel文件，写入列表
     */
    public static <T extends Object & Serializable> void writeTemplateWithList(File file, List<T> list, Class<T> clazz, int headerSize) {
        List<TreeMap<String, String>> dataGridRowList = ExcelConverter.to(list, clazz, false, headerSize); // 模板中已经有表头，不需要再次组装表头
        ExcelUtils.writeTemplateWithList(file, dataGridRowList, headerSize); // 参数headerSize表头有多少行
    }

    /**
     * 读取Excel文件，写入对象
     */
    public static void writeTemplateWithObject(File file, TreeMap<String, String> dataGridMap) {
        ExcelUtils.exportForBrowser(
                new ExcelDocument(file),
                file.getName(),
                (wb) -> wb.writeTemplateWithObject(dataGridMap)
        );
    }

    /**
     * 读取Excel文件，写入对象
     */
    public static <T extends Object & Serializable> void writeTemplateWithObject(File file, T object, Class<T> clazz) {
        TreeMap<String, String> dataGridMap = ExcelConverter.to(object, clazz); // 这里获取到的是一个 Java POJO 对象每个数据成员在Sheet页的具体位置，不存在表头
        ExcelUtils.writeTemplateWithObject(file, dataGridMap); // 将 Java POJO 对象的 数据成员 填充到 Sheet 模板页
    }

    // endregion

    // region 新建Excel文件写入(导出)操作【默认仅导出了一个Sheet页，但是ExcelDocument已经支持在单个Excel文件中同时导出若干个Sheet页，可根据需要扩展】

    /**
     * 创建Excel文件，写入列表
     */
    public static void writeWithList(WorkbookType workbookType, String exportFileName, List<TreeMap<String, String>> dataGridRowList) {
        ExcelUtils.exportForBrowser(
                new ExcelDocument(workbookType),
                exportFileName + workbookType.getSuffix(),
                (wb) -> wb.writeWithList(dataGridRowList)
        );
    }

    /**
     * 创建Excel文件，写入列表
     */
    public static <T extends Object & Serializable> void writeWithList(WorkbookType workbookType, String exportFileName, List<T> list, Class<T> clazz) {
        List<TreeMap<String, String>> dataGridRowList = ExcelConverter.to(list, clazz, true, 1); // 空白的Sheet页，需要根据 Java POJO 的配置组装表头，目前的封装仅支持一维表头
        ExcelUtils.writeWithList(workbookType, exportFileName, dataGridRowList);
    }

    /**
     * 创建Excel文件，写入对象
     */
    public static void writeWithObject(WorkbookType workbookType, String exportFileName, TreeMap<String, String> dataGridMap) {
        ExcelUtils.exportForBrowser(
                new ExcelDocument(workbookType),
                exportFileName + workbookType.getSuffix(),
                (wb) -> wb.writeWithObject(dataGridMap)
        );
    }

    /**
     * 创建Excel文件，写入对象
     */
    public static <T extends Object & Serializable> void writeWithObject(WorkbookType workbookType, String exportFileName, T object, Class<T> clazz) {
        TreeMap<String, String> dataGridMap = ExcelConverter.to(object, clazz); // 这里获取到的是一个 Java POJO 对象每个数据成员在Sheet页的具体位置，不存在表头
        ExcelUtils.writeWithObject(workbookType, exportFileName, dataGridMap); // 将 Java POJO 对象的 数据成员 填充到 Sheet 空白页(这个场景在实际中没有太大的意义，缺乏表头为数据描述实际的业务含义)
    }

    // endregion

    // region others

    /**
     * 基于客户端浏览器的保存
     *
     * @param workbook 工作薄
     * @param fileName 导出文件名
     * @param consumer 将数据写入工作薄并执行导出的具体动作
     */
    static void exportForBrowser(ExcelDocument workbook, String fileName, Consumer<ExcelDocument> consumer) {
        // 获取请求对象，记录发送导出文件请求的客户端浏览器类型，以及发送该请求的域名
        HttpServletRequest request = ServletHelper.getRequest();
        log.info("当前请求执行写入操作的域名是：{}，使用的浏览器是：{}", request.getServerName(), request.getHeader("USER-AGENT").toLowerCase());

        // 设置默认的导出样式，也可根据需要在外部定制其它的导出样式。目前的封装仅支持一级表头，多级表头与复杂样式使用编码撸出来的实现成本是不划算的，与硬编码的实现方式相比，使用Excel模板导出则非常便利。
        workbook.setDefaultSheetStyle(ExcelStyle::setDefaultSheetStyle);
        workbook.setTitleCellStyle(ExcelStyle::setDefaultTitleCellStyle);
        workbook.setContentCellStyle(ExcelStyle::setDefaultContentCellStyle);

        // 执行数据写入的操作
        consumer.accept(workbook);

        // 获取响应对象
        int lastDotIndex = fileName.lastIndexOf(".");
        String prefixName = fileName.substring(0, lastDotIndex);
        String suffixName = fileName.substring(lastDotIndex);
        HttpServletResponse response = ServletHelper.getResponse();
        String exportFileName = prefixName + "_" + UUID.randomUUID().toString().replaceAll("-", "") + "_" + System.nanoTime() + suffixName;
        workbook.exportWorkbookAs(exportFileName, response);
        workbook.closeWorkbook();
    }

    // endregion

}
